package com.luoke.util;

import com.luoke.constant.DatabaseConstants;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;


/**
 * 该类是数据库操作通用工具类
 * 这里解释一下为什么我没有写数据库连接对象close()的方法
 * 因为使用了try-with-resources，在底层会自动调用对应close()方法
 */
public class DatabaseUtil {

    // 获取数据库连接
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(DatabaseConstants.DB_URL, DatabaseConstants.DB_USER, DatabaseConstants.DB_PASSWORD);
    }

    /**
     * 插入新记录
     * 该方法使用反射来生成 INSERT 语句，并将对象字段作为参数传递。
     *
     * @param obj 要插入数据库的对象
     * @return 插入成功时返回生成的主键 ID，否则返回影响的行数
     */
    public static <T> int insert(T obj) throws SQLException, IllegalAccessException {
        StringBuilder sql = new StringBuilder("INSERT INTO ");
        StringBuilder placeholders = new StringBuilder("(");
        StringBuilder columns = new StringBuilder("(");

        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields(); // 获取对象的所有字段

        // 表名取类名的小写形式
        sql.append(clazz.getSimpleName().toLowerCase()).append(" ");

        // 遍历字段，构建列名和占位符，只处理非静态字段
        int parameterIndex = 0;
        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers())) {
                // 跳过静态字段
                continue;
            }
            field.setAccessible(true);
            columns.append(camelToSnake(field.getName())); // 将字段名转换为 驼峰映射后的列名
            placeholders.append("?");
            parameterIndex++;
            if (parameterIndex < fields.length) {
                columns.append(", ");
                placeholders.append(", ");
            }
        }

        // 移除末尾的多余逗号和空格    下一个方法(update)有解释使用setLength的原因
        columns.setLength(columns.length() - 2);
        placeholders.setLength(placeholders.length() - 2);

        columns.append(")");
        placeholders.append(")");

        // 拼接完整的 SQL 语句
        sql.append(columns).append(" VALUES ").append(placeholders);

        try (Connection conn = getConnection();                                     //设置主键回显
             PreparedStatement pstmt = conn.prepareStatement(sql.toString(), Statement.RETURN_GENERATED_KEYS)) {

            // 设置参数值，只对非静态字段赋值
            int index = 1;
            for (Field field : fields) {
                if (Modifier.isStatic(field.getModifiers())) {
                    continue; // 跳过静态字段
                }
                pstmt.setObject(index++, field.get(obj));
            }
            //TODO 测试代码，后续删除
            System.out.println(sql);
            // 执行 SQL 语句
            int affectedRows = pstmt.executeUpdate();
            if (affectedRows > 0) {
                // 将 ResultSet 放入 try-with-resources 以便自动关闭资源
                try (ResultSet generatedKeys = pstmt.getGeneratedKeys();) {
                    if (generatedKeys.next()) {
                        return generatedKeys.getInt(1); // 返回生成的主键 ID
                    }
                }
            }
            return affectedRows;
        }
    }


    /**
     * 更新记录
     * 该方法根据对象字段构建 UPDATE 语句，用指定 ID 列的值定位记录。
     *
     * @param obj      包含新值的对象
     * @param idColumn 要更新记录的 ID 列名
     * @param idValue  要更新记录的 ID 值
     * @return 影响的行数
     */
    public static <T> int update(T obj, String idColumn, Object idValue) throws SQLException, IllegalAccessException {
        StringBuilder sql = new StringBuilder("UPDATE ");
        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();  //使用反射拿到字段

        sql.append(clazz.getSimpleName().toLowerCase()).append(" SET "); //得到类名的小写作为表名

        List<Object> values = new ArrayList<>(); // 存储非 `null` 字段的值

        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers())) {
                continue; // 跳过静态字段
            }
            field.setAccessible(true); //设置可访问private字段
            Object value = field.get(obj);
            if (value != null) { // 仅更新非 `null` 字段
                sql.append(camelToSnake(field.getName())).append(" = ?, "); // 将字段名转换为 驼峰映射后的列名
                values.add(value);
            }
        }

        // 移除末尾多余的逗号和空格
        // 例如： sql.append(camelToSnake(field.getName())).append(" = ?, ");  这里执行到最后就会有一个逗号和空格，这会导致sql语句出错
        sql.setLength(sql.length() - 2); //setLength  直接把长度设置为总长的前两位，这样就可以直接移除最后两个多余 (这里使用substring方法也行，不过这个我个人觉得更方便一些)

        //添加WHERE 子句
        sql.append(" WHERE ").append(idColumn).append(" = ?");
        values.add(idValue); // 将 需要更新的值添加到参数列表末尾

        try (Connection conn = getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql.toString())) {

            // 设置参数值
            for (int i = 0; i < values.size(); i++) {
                pstmt.setObject(i + 1, values.get(i));
            }
            // TODO 测试代码，后续删除
            System.out.println(sql);
            return pstmt.executeUpdate(); // 执行更新并返回受影响的行数
        }
    }

    // 直接执行 SQL 更新语句的 update 方法重载
    public static int update1(String sql, Object... params) throws SQLException {
        try (Connection conn = getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            for (int i = 0; i < params.length; i++) {
                pstmt.setObject(i + 1, params[i]);
            }
            return pstmt.executeUpdate();
        }
    }

    /**
     * 删除记录
     * 该方法生成 DELETE 语句，使用 ID 列的值定位记录。
     *
     * @param tableName 要删除的表名
     * @param idColumn  ID 列名
     * @param idValue   ID 值
     * @return 影响的行数
     */
    public static int delete(String tableName, String idColumn, Object idValue) throws SQLException {
        String sql = "DELETE FROM " + tableName + " WHERE " + idColumn + " = ?";
        try (Connection conn = getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setObject(1, idValue);
            return pstmt.executeUpdate();
        }
    }

    /**
     * 查询单个对象
     * 该方法生成 SELECT 语句，并将结果集的第一行转换为指定类型的对象。
     *
     * @param sql    查询 SQL 语句
     * @param clazz  结果对象的类类型
     * @param params 查询参数
     * @return 查询到的对象
     */
    public static <T> T selectOne(String sql, Class<T> clazz, Object... params) throws SQLException, ReflectiveOperationException {
        try (Connection conn = getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql)) {

            for (int i = 0; i < params.length; i++) {
                pstmt.setObject(i + 1, params[i]);
            }

            ResultSet rs = pstmt.executeQuery();
            if (rs.next()) {
                T obj = clazz.getDeclaredConstructor().newInstance();
                populateObject(rs, obj); // 调用填充方法
                return obj;
            }
            return null;
        }
    }

    /**
     * 查询多个对象
     * 该方法传入 SELECT 语句，并将每行结果转换为指定类型的对象。
     * TODO 该方法暂未测试
     *
     * @param sql    查询 SQL 语句
     * @param clazz  结果对象的类类型
     * @param params 查询参数
     * @return 对象的列表
     */
    public static <T> List<T> selectAll(String sql, Class<T> clazz, Object... params) throws SQLException, ReflectiveOperationException {
        List<T> results = new ArrayList<>();
        try (Connection conn = getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql)) {

            for (int i = 0; i < params.length; i++) {
                pstmt.setObject(i + 1, params[i]);
            }

            ResultSet rs = pstmt.executeQuery();
            while (rs.next()) {
                if (clazz==String.class){     //如果是String类型不用填充字段
                    results.add((T) rs.getObject("name"));
                }else {
                    T obj = clazz.getDeclaredConstructor().newInstance();
                    populateObject(rs, obj); // 调用填充方法
                    results.add(obj);
                }

            }
        }
        return results;
    }

    /**
     * 填充对象字段
     * 该方法使用反射将 ResultSet 中的列值填充到对象的相应字段中。
     *
     * @param rs  查询结果集
     * @param obj 要填充的对象
     */
    private static <T> void populateObject(ResultSet rs, T obj) throws SQLException, IllegalAccessException {
        Field[] fields = obj.getClass().getDeclaredFields(); //这里通过反射拿到字段名
        for (Field field : fields) {

            //因为实体类中有序列化所需要标记的字段，这里排除掉
            if (Modifier.isStatic(field.getModifiers())) {  //该方法用来判断是否是静态成员变量
                continue; // 跳过静态字段
            }

            field.setAccessible(true); // 设置private字段可访问

            String columnName = camelToSnake(field.getName()); // 转换为下划线命名


            Object value = null;
            try {
                if (columnName.equals("create_time") || columnName.equals("last_login")) {
                    //从 ResultSet 获取列值
                    value = rs.getObject(columnName, LocalDateTime.class);
                    field.set(obj, value); //给字段设置值
                } else {
                    value = rs.getObject(columnName);
                    field.set(obj, value);
                }

            } catch (SQLException e) {
                // 当列不存在时捕获异常并跳过该字段
                System.out.println("Column not found: " + columnName);
            }


        }
    }


    /**
     * 将驼峰命名转换为下划线命名
     * 例如：createTime -> create_time
     *
     * @param camelCase 字段名（驼峰命名）
     * @return 数据库字段名（下划线命名）
     */
    private static String camelToSnake(String camelCase) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < camelCase.length(); i++) {
            char c = camelCase.charAt(i); // 获取当前字符
            if (Character.isUpperCase(c)) {  //判断该字符是否是大写
                result.append('_').append(Character.toLowerCase(c)); //如果是大写则添加下划线并转换为小写
            } else {
                result.append(c); //不是则直接添加
            }
        }
        return result.toString();
    }
}
